
#ifndef __RSP_S_CONNECTOR__HEADER_FILE__
#define __RSP_S_CONNECTOR__HEADER_FILE__

#include "coid/comm/dynarray.h"
#include "coid/comm/str.h"
//#include "coid/comm/binstream/circularstreambuf.h"
#include "coid/comm/binstream/netstreamudp.h"

#include "fcbcont.h"
#include "s_update.h"
//#include "s_objlist.h"


////////////////////////////////////////////////////////////////////////////////
///Attach request
struct AttachReq
{
    uint32 rqid;                ///< request id so the client knows to which request this is the response
    uint32 ssid;                ///< session id, 0 if new, otherwise client might be trying to reconnect
    uint lag;
    netAddress addr;

    uchar rqdata[256];          ///< additional request data
    uint rqlen;                 ///< additional request data length, max 256

    uint triptime() const       { return 2*lag; }
};


////////////////////////////////////////////////////////////////////////////////
///Command header packer and extractor for private (connector specific) objects
struct CmdHeaderPrivate
{
    enum {
        SIZE_BITS               = 8,
        fFULL_STATE             = 1<<SIZE_BITS,
        rOID                    = 1+SIZE_BITS,
    };

    static uchar incremental( const uchar*& pd, uint& oid )
    {
        uint oidl = *(const uint*)pd;
        if( oidl & fFULL_STATE )
            //this should happen on the client only
            //full state updates have to be applied after frame() methods
            return 0;

        pd += sizeof(uint);
        oid = oidl >> rOID;
        return (uchar)oidl;
    }

    static ushort full( const uchar*& pd, uint& oid, token& clsid )
    {
        uint oidl = *(const uint*)pd;
        DASSERT( oidl & fFULL_STATE );

        pd += sizeof(uint);
        oid = oidl >> rOID;
        ushort len = (uchar)oidl;
        clsid.set( (const char*)pd, len );
        pd += len;
        len = *(const ushort*)pd;
        pd += sizeof(ushort);
        return len;
    }

    static void* pack_incremental( frame_command_buffer& fcb, uint oid, uchar len )
    {
        uint* pd = (uint*)fcb.add( sizeof(uint) + len );
        *pd = (oid << rOID) | len;
        return pd+1;
    }

    static uint pack_full( frame_command_buffer& fcb, uint oid, const token& clsid )
    {
        uint* pd = (uint*)fcb.add( sizeof(oid) + clsid.len() + sizeof(ushort) );
        *pd = (oid << rOID) | fFULL_STATE | clsid.len();
        ::memcpy( pd+1, clsid.ptr(), clsid.len() );
        return fcb.size();
    }
};

////////////////////////////////////////////////////////////////////////////////
///Server side object rsp_s_object manager
struct rsp_s_connector : fcb_container<CmdHeaderPrivate>
{
    rsp_s_connector()
    {
        _ssid = 0;
        reset();
    }

    void reset()
    {
        _cstate = CS_DISCONNECTED;
        _ackstateid = RSP_SETTINGS.active_frame() - RSP_SETTINGS._ntrailing - 1;

        _nick.reset();
        //_ssid = 0;
    }


    //void set_owned_object( uint oid )           { set_owned_object( rsp_base::from_oid(oid) ); }
    void set_owned_object( rsp_base* p )        { _owned = p; }

    uint get_acked_frame() const                { return _ackstateid; }

    const charstr& get_nick() const             { return _nick; }

    uint32 get_ssid() const                     { return _ssid; }
    void init_ssid( uint32 ssid )
    {
        _cstate = CS_INITIAL;
        _ssid = ssid;
    }
    void term_ssid()
    {
        _cstate = CS_DISCONNECTED;
        _ssid = 0;
    }

    ///@return true if the connector is currently used by a client
    bool is_taken() const                       { return _cstate >= CS_INITIAL; }

    bool is_dead() const
    {
        //give some more time to the newly connected clients
        if( _cstate < CS_INCREMENTAL )
            return int(RSP_SETTINGS.active_frame() - _ackstateid) > (int)RSPS_SETTINGS._nkickoffstart_frames;
        else
            return int(RSP_SETTINGS.active_frame() - _ackstateid) > (int)RSPS_SETTINGS._nkickoff_frames;
    }


    void drop()
    {
        if( _cstate == CS_DISCONNECTED  ||  _cstate == CS_LEAVING )  return;
        
        _cstate = CS_LEAVING;
        _ackstateid = RSP_SETTINGS.active_frame();
    }

    ///Generate the differential update into the FCB
    /**
        This method is called each network replication frame, and generates an update packet for
        the client.

        The update packet contains commands and diffs for each managed object. Normally server sends
        only the commands, but if some object's field was changed by another means than by a command,
        or indirectly by a command of another object that isn't in the replicated set, server sends
        the changed fields. The changes should be applied in the reference state. Otherwise, server
        sends all commands from last acknowledged state up to the last known wait state.

        Server sends message when refstate changes.
    **/
    bool diff( netstreamudp& net )
    {
        if( is_dead() )
            drop();

        if( _cstate == CS_DISCONNECTED )  return false;
        if( _cstate == CS_LEAVING )
        {
            //send disconnects until the client alone acknowledges it or after some frames
            net << (uint8)RSP_PACKET::DISCONNECT << BINSTREAM_FLUSH;

            int n = RSP_SETTINGS.active_frame() - _ackstateid;
            if( n > (int)RSPS_SETTINGS._nkickoff_frames ) {
                _cstate = CS_DISCONNECTED;
                return false;
            }
            return true;
        }

        if( _cstate == CS_INITIAL )
            _cstate = CS_UNACKED;

        frame_command_buffer& fcb = (frame_command_buffer&)net.get_send_buffer();

        uint stateid = _cstate == CS_UNACKED
            ? RSP_SETTINGS.active_frame()
            : _ackstateid;

        int ntrailskip = RSP_SETTINGS.active_frame() - stateid;
        bool full = ntrailskip == 0  ||  ntrailskip > (int)RSP_SETTINGS._ntrailing;

        if(full)
        {
            stateid = RSP_SETTINGS.active_frame();
            LOG(2) {
                if( _cstate == CS_INCREMENTAL )
                    *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " switching back to full updates,  ack frame: ";
                else
                    *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " initial full update,  ack frame: ";
                *TLOG << timefmt(_ackstateid) << ", delta: " << (RSP_SETTINGS.active_frame()-_ackstateid) << BINSTREAM_FLUSH;
            }

            //send full frame id
            fcb << (uint8)RSP_PACKET::FULL << _Timer.time()
                << RSP_SETTINGS.active_frame();

            create_full_update(fcb);
        }
        else
        {
            //send short frame id and short acknowledged frame id
            //differential update cannot be far off from current active frame, maybe even shorter id's would suffice
            fcb << (uint8)RSP_PACKET::DIFF << _Timer.time()
                << (ushort)RSP_SETTINGS.active_frame() << (ushort)stateid;

            //rsp_s_update upd( this, fcb, stateid );
            create_differential_update( fcb, ntrailskip - RSP_SETTINGS._ntrailing );
        }

        net.flush();
        return true;
    }


    ///Apply commands from client
    /// @return true if the message was read successfully
    int patch( netstreamudp& bin )
    {
        ushort stid;
        bin >> stid;    //acknowledged state (server state received by the client)

        short d = stid - (ushort)_ackstateid;
        if( d < 0 )
        {
            LOG(1) {
                *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " AMBIGUOUS patch for "
                    << timefmt(_ackstateid) << " (" << d << ")" << BINSTREAM_FLUSH;
            }
            return 0;
        }

        if( _ackstateid+d > RSP_SETTINGS.active_frame() ) {
            LOG(1) {
                *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " FUTURE ack for "
                    << timefmt(_ackstateid+d) << " +"
                    << (_ackstateid+d-RSP_SETTINGS.active_frame()) << BINSTREAM_FLUSH;
            }
            return 0;
        }

        _ackstateid += d;

        if( _cstate != CS_INCREMENTAL )
            LOG(2) { *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " ack message for " << timefmt(_ackstateid) << BINSTREAM_FLUSH; }

        _cstate = CS_INCREMENTAL;
        //_list.set_incremental_mode();

        uchar ncmd;
        bin >> ncmd;

        if(!ncmd) {
            bin.acknowledge();
            return -1;//1;
        }

        if(!_owned)
        {
            LOG(1) { *TLOG << "message for unowned object ignored\n"; }
            return 0;
        }

        const uchar* pd, *pe;
        uint len;
        if( !bin.get_raw_unread_data(pd,len) )  return 0;

        pe = pd + len;
        return _owned->read_cmd( ncmd, _ackstateid, pd, pe );
    }


protected:

    uint32 _ssid;                           ///< session id
    charstr _nick;                          ///< nickname of connected client - for logging purposes

    uint _ackstateid;                       ///< last frame number acknowledged by the client

    int  _cstate;                           ///< connection state, see enum
    enum {
        CS_DISCONNECTED         = -1,       ///< not connected
        CS_INITIAL              = 0,        ///< initial state after received connection request
        CS_UNACKED              = 1,        ///< state until the first server message is acknowledged, or after desync
        CS_INCREMENTAL          = 2,        ///< client set up to receive incremental updates - running ok
        CS_LEAVING              = 3,        ///< connector leaving the server - remaining while trailing queue flushed
    };

    rsp_base* _owned;                       ///< controlled object
};



#endif //__RSP_S_CONNECTOR__HEADER_FILE__
